{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "# TensorFlow 애드온 손실: TripletSemiHardLoss\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/addons/tutorials/losses_triplet\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/addons/tutorials/losses_triplet.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/addons/tutorials/losses_triplet.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/addons/tutorials/losses_triplet.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xHxb-dlhMIzW"
      },
      "source": [
        "## 개요\n",
        "\n",
        "이 노트북은 TensorFlow 애드온에서 TripletSemiHardLoss 함수를 사용하는 방법을 보여줍니다.\n",
        "\n",
        "### 리소스:\n",
        "\n",
        "- [FaceNet : 얼굴 인식 및 클러스터링을위한 통합 임베딩](https://arxiv.org/pdf/1503.03832.pdf)\n",
        "- [Oliver Moindrot의 블로그는 알고리즘을 자세히 설명하는 훌륭한 작업을 수행합니다.](https://omoindrot.github.io/triplet-loss)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bQwBbFVAyHJ_"
      },
      "source": [
        "## TripletLoss\n",
        "\n",
        "FaceNet 논문에 처음 소개된 TripletLoss는 신경망을 훈련하여 같은 클래스의 특성을 밀접하게 포함하면서 서로 다른 클래스의 임베딩 간의 거리를 최대화합니다. 이를 위해 하나의 음수 샘플과 하나의 양수 샘플과 함께 앵커가 선택됩니다. ![fig3](https://user-images.githubusercontent.com/18154355/61485418-1cbb1f00-a96f-11e9-8de8-3c46eef5a7dc.png)\n",
        "\n",
        "**손실 함수는 Euclidean 거리 함수로 설명됩니다.**\n",
        "\n",
        "![function](https://user-images.githubusercontent.com/18154355/61484709-7589b800-a96d-11e9-9c3c-e880514af4b7.png)\n",
        "\n",
        "A는 앵커 입력이고, P는 양의 샘플 입력이고, N은 음의 샘플 입력이며, 알파는 트리플릿이 너무 \"쉬워지는\" 시점을 지정하기 위해 사용하는 한계입니다. ."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wPJ5521HZHeL"
      },
      "source": [
        "## SemiHard 온라인 학습\n",
        "\n",
        "이 논문에서 볼 수 있듯이 가장 좋은 결과는 \"Semi-Hard\"로 알려진 트리플릿에서 얻습니다. 트리플릿은 음이 양보다 앵커에서 더 멀리 있는 트리플릿으로 정의되지만, 여전히 양의 손실을 생성합니다. 이러한 트리플릿을 효율적으로 찾기 위해 온라인 학습을 활용하고 각 배치에서 Semi-Hard 예제를 통해서만 훈련합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MUXex9ctTuDB"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6Vyo25M2ba1P"
      },
      "outputs": [],
      "source": [
        "!pip install -U tensorflow-addons"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IqR2PQG4ZaZ0"
      },
      "outputs": [],
      "source": [
        "import io\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WH_7-ZYZYblV"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "import tensorflow_addons as tfa\n",
        "import tensorflow_datasets as tfds"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0_D7CZqkv_Hj"
      },
      "source": [
        "## 데이터 준비하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iXvByj6wcT7d"
      },
      "outputs": [],
      "source": [
        "def _normalize_img(img, label):\n",
        "    img = tf.cast(img, tf.float32) / 255.\n",
        "    return (img, label)\n",
        "\n",
        "train_dataset, test_dataset = tfds.load(name=\"mnist\", split=['train', 'test'], as_supervised=True)\n",
        "\n",
        "# Build your input pipelines\n",
        "train_dataset = train_dataset.shuffle(1024).batch(32)\n",
        "train_dataset = train_dataset.map(_normalize_img)\n",
        "\n",
        "test_dataset = test_dataset.batch(32)\n",
        "test_dataset = test_dataset.map(_normalize_img)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KR01t9v_fxbT"
      },
      "source": [
        "## 모델 빌드하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wvOPPuIKhLJi"
      },
      "source": [
        "![fig2](https://user-images.githubusercontent.com/18154355/61485417-1cbb1f00-a96f-11e9-8d6a-94964ce8c4db.png)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "djpoAvfWNyL5"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential([\n",
        "    tf.keras.layers.Conv2D(filters=64, kernel_size=2, padding='same', activation='relu', input_shape=(28,28,1)),\n",
        "    tf.keras.layers.MaxPooling2D(pool_size=2),\n",
        "    tf.keras.layers.Dropout(0.3),\n",
        "    tf.keras.layers.Conv2D(filters=32, kernel_size=2, padding='same', activation='relu'),\n",
        "    tf.keras.layers.MaxPooling2D(pool_size=2),\n",
        "    tf.keras.layers.Dropout(0.3),\n",
        "    tf.keras.layers.Flatten(),\n",
        "    tf.keras.layers.Dense(256, activation=None), # No activation on final dense layer\n",
        "    tf.keras.layers.Lambda(lambda x: tf.math.l2_normalize(x, axis=1)) # L2 normalize embeddings\n",
        "\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HYE-BxhOzFQp"
      },
      "source": [
        "## 훈련 및 평가하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NxfYhtiSzHf-"
      },
      "outputs": [],
      "source": [
        "# Compile the model\n",
        "model.compile(\n",
        "    optimizer=tf.keras.optimizers.Adam(0.001),\n",
        "    loss=tfa.losses.TripletSemiHardLoss())\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TGBYNGxgVDrj"
      },
      "outputs": [],
      "source": [
        "# Train the network\n",
        "history = model.fit(\n",
        "    train_dataset,\n",
        "    epochs=5)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1Y--0tK69SXf"
      },
      "outputs": [],
      "source": [
        "# Evaluate the network\n",
        "results = model.predict(test_dataset)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dqSuLdVZGNrZ"
      },
      "outputs": [],
      "source": [
        "# Save test embeddings for visualization in projector\n",
        "np.savetxt(\"vecs.tsv\", results, delimiter='\\t')\n",
        "\n",
        "out_m = io.open('meta.tsv', 'w', encoding='utf-8')\n",
        "for img, labels in tfds.as_numpy(test_dataset):\n",
        "    [out_m.write(str(x) + \"\\n\") for x in labels]\n",
        "out_m.close()\n",
        "\n",
        "\n",
        "try:\n",
        "  from google.colab import files\n",
        "  files.download('vecs.tsv')\n",
        "  files.download('meta.tsv')\n",
        "except:\n",
        "  pass"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VAtj_m6Z_Uwe"
      },
      "source": [
        "## Embedding Projector"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y4rjlG9rlbVA"
      },
      "source": [
        "벡터 및 메타 데이터 파일은 https://projector.tensorflow.org/에서 로드하고 시각화할 수 있습니다.\n",
        "\n",
        "UMAP으로 시각화하면 포함된 테스트 데이터의 결과를 볼 수 있습니다. ![embedding](https://user-images.githubusercontent.com/18154355/61600295-e6470380-abfd-11e9-8a00-2b25e7e6916f.png)\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "losses_triplet.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
